package org.mybatis.demo.plugins;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 物理分页插件
 *
 * @author yangjian
 */
@Intercepts({
  @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
  @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class
  })})
public class PageInterceptor implements Interceptor {

  private static final String LIMIT_PARAM_NAME = "__PAGE_LIMIT__";
  private static final String OFFSET_PARAM_NAME = "__PAGE_OFFSET__";

  public PageInterceptor()
  {
    System.out.println("=========================================================");
    System.out.println("====================== 初始化分页插件 =====================");
    System.out.println("=========================================================");
  }

  @Override
  public Object intercept(Invocation invocation) throws Throwable
  {
    // 接收参数
    Object[] args = invocation.getArgs();
    MappedStatement mappedStatement = (MappedStatement) args[0];
    RowBounds rowBounds = (RowBounds) args[2];
    Object parameter = args[1];
    ResultHandler resultHandler = (ResultHandler) args[3];
    Executor executor = (Executor) invocation.getTarget();

    // 处理参数
    CacheKey cacheKey;
    BoundSql boundSql;
    if (args.length == 4) {
      // 4 个参数
      boundSql = mappedStatement.getBoundSql(parameter);
      cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
    } else {
      // 6 个参数
      cacheKey = (CacheKey) args[4];
      boundSql = (BoundSql) args[5];
    }

    // 获取分页信息
    Page page = PageHelper.getPage();
    if (null == page) { // 不用处理分页
      return invocation.proceed();
    }

    // 拼接 LIMIT 分页
    String pageSql;
    if (page.getPageNo() == 1) {
      pageSql = boundSql.getSql() + " LIMIT ?";
    } else {
      pageSql = boundSql.getSql() + " LIMIT ?, ?";
    }

    // 设置分页参数类型
    List<ParameterMapping> newParameterMappings = new ArrayList<>(boundSql.getParameterMappings());
    if (boundSql.getParameterMappings() != null) {
      if (page.getPageNo() == 1) {
        newParameterMappings.add(new ParameterMapping.Builder(mappedStatement.getConfiguration(), LIMIT_PARAM_NAME, int.class).build());
      } else {
        newParameterMappings.add(new ParameterMapping.Builder(mappedStatement.getConfiguration(), OFFSET_PARAM_NAME, long.class).build());
        newParameterMappings.add(new ParameterMapping.Builder(mappedStatement.getConfiguration(), LIMIT_PARAM_NAME, int.class).build());
      }
    }
    BoundSql pageBoundSql = new BoundSql(mappedStatement.getConfiguration(), pageSql, newParameterMappings, parameter);
    // 绑定分页参数
    if (page.getPageNo() > 1) {
      long offset = (page.getPageNo() - 1) * page.getPageSize();
      pageBoundSql.setAdditionalParameter(OFFSET_PARAM_NAME, offset);
    }
    pageBoundSql.setAdditionalParameter(LIMIT_PARAM_NAME, page.getPageSize());

    // 清空分页信息
    PageHelper.removePage();
    // 执行
    return executor.query(mappedStatement, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
  }

  @Override
  public Object plugin(Object target)
  {
    return Interceptor.super.plugin(target);
  }

  @Override
  public void setProperties(Properties properties)
  {
    Interceptor.super.setProperties(properties);
  }
}
